package core;

import fileoperation.FileCreation;
import fileoperation.FileDeletion;
import gitobject.*;
import repository.Repository;

import java.io.File;
import java.io.IOException;
import java.util.*;

public class JitMap {
    public static Map<String, String> commitMap = new HashMap<>();
    public static JitIndex index = JitIndex.deserialize();

    public static void initial(){
        try {
            setCommitMap(Commit.getLastCommit());
        } catch (IOException e) {
            e.printStackTrace();
        }
        setIndexMap();
    }

    public static void setCommitMap(String commitId){
        commitMap.clear();
        index.clearStatus();
        readAllCommit(commitMap, commitId, true);
    }

    public static void readAllCommit(Map<String, String> treeMap, String commitId, boolean changeStatus){
        try{
            while(!Objects.equals(commitId, "" ) && !Objects.equals(commitId, null)){
                Commit commit = Commit.deserialize(commitId);
                if(commit == null) {
                    System.out.println("fatal: unknown commit '" + commitId + "'.");
                    return;
                }
                String treeId = commit.getTree();
                Tree commitTree = Tree.deserialize(treeId);
                if(commitTree == null) {
                    System.out.println("fatal: unknown tree '" + treeId + "'.");
                    return;
                }
                setKeyForMap(treeMap, commitTree, ".", changeStatus);
                commitId = commit.getParent();
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void setIndexMap(){
        ArrayList<String> reMatch = new ArrayList<>();
        ArrayList<String> indexToStatus = new ArrayList<>();
        for (Map.Entry<String, String> item : index.getMapValues()) {
            String keyPath = item.getKey();
            if(commitMap.containsKey(keyPath) && Objects.equals(commitMap.get(keyPath), index.getIndexKey(keyPath))){
                reMatch.add(keyPath);
            }
            indexToStatus.add(keyPath);
        }
        for(String keyPath : reMatch){
            index.removeIndex(keyPath);
        }
        ArrayList<String> missMatch = new ArrayList<>();
        for (Map.Entry<String, Integer> item : index.getStatusValues()) {
            String keyPath = item.getKey();
            if((Objects.equals(index.getStatus(keyPath), JitIndex.modified) || Objects.equals(index.getStatus(keyPath), JitIndex.newFile)) && !index.contains(keyPath)){
                missMatch.add(keyPath);
            }
        }
        for(String keyPath : missMatch){
            index.removeStatus(keyPath);
        }
        for (String keyPath : indexToStatus){
            JitMap.changeStatusInIndex(keyPath);
        }
        clearUntrackedStatus();
        setUntrackedToStatus(".");
    }

    public static void setCommitMapResetBlob(String commitId, boolean hardMode){
        try {
            commitMap.clear();
            index.clearStatus();
            while (!Objects.equals(commitId, "") && !Objects.equals(commitId, null)) {
                Commit commit = Commit.deserialize(commitId);
                assert commit != null;
                String treeId = commit.getTree();
                Tree commitTree = Tree.deserialize(treeId);
                assert commitTree != null;
                resetWorkTreeWithMap(commitMap, commitTree, ".");
                commitId = commit.getParent();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(hardMode) {
            JitMap.clearUntrackedStatus();
            JitMap.setUntrackedToStatus(".");
            deleteUntrackedFiles(".");
        }
    }

    public static void setKeyForMap(Map<String, String> treeMap, Tree tree, String path, boolean changeStatus){
        try {
            Vector<String[]> objectValue = tree.readValue();
            if(!objectValue.isEmpty()){
                for(String[] line: objectValue){
                    String subObjectFmt = line[1];
                    String subObjectKey = line[2];
                    String subObjectName = line[3];
                    String newPath = Objects.equals(path, ".") ? subObjectName : path + File.separator + subObjectName;
                    String keyPath = newPath.replace(File.separator, Tree.treeSeparator);
                    if(!treeMap.containsKey(keyPath)) {
                        //System.out.println(tree.getKey());
                        GitObject gitObject = GitObject.deserialize(subObjectKey);
                        assert gitObject  != null;
                        if (Objects.equals(subObjectFmt, "tree")) {
                            Tree subTree = (Tree) gitObject;
                            if(changeStatus)changeStatusInCommit(keyPath);
                            setKeyForMap(treeMap, subTree, newPath, changeStatus);
                        }
                        else if(Objects.equals(subObjectFmt, "blob")){
                            Blob blob = (Blob) gitObject;
                            if(treeMap instanceof HashMap) {
                                treeMap.put(keyPath, subObjectKey);
                                if(changeStatus)changeStatusInCommit(keyPath);
                            }
                            else if(treeMap instanceof TreeMap) {
                                Date date = new Date();
                                String indexContent = blob.toString() + " " + date.toString() + " " + keyPath;
                                treeMap.put(keyPath, indexContent);
                                if(changeStatus)changeStatusInIndex(keyPath);
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void resetWorkTreeWithMap(Map<String, String> commitMap, Tree t, String parentTree) throws IOException {
        Vector<String[]> objectValue = t.readValue();
        for(String[] line : objectValue){
            String objectKey = line[2];
            String subName = line[3];
            String newPath = Objects.equals(parentTree, ".") ? subName : parentTree + File.separator + subName;
            String keyPath = newPath.replace(File.separator, Tree.treeSeparator);
            if(line[1].equals("blob")){
                Blob blob = Blob.deserialize(objectKey);
                if(blob == null) System.out.printf("fatal: blob '%s' is lost", objectKey);
                else {
                    if(!commitMap.containsKey(keyPath)) {
                        FileCreation.createFile(Repository.getWorkTree() + File.separator + parentTree, subName, blob.getValue());
                        commitMap.put(keyPath, objectKey);
                        changeStatusInCommit(keyPath);
                    }
                }
            }
            else if(line[1].equals("tree")){
                Tree tree = Tree.deserialize(objectKey);
                new File(Repository.getWorkTree() + File.separator + newPath).mkdir();
                if(tree == null) System.out.printf("fatal: tree '%s' is lost", objectKey);
                else {
                    changeStatusInCommit(keyPath);
                    resetWorkTreeWithMap(commitMap, tree, newPath);
                }
            }
        }
    }

    public static Set<String> getCommitHistorySet(String commitId) throws IOException{
        Set<String> commitSet = new HashSet<>();
        while(!Objects.equals(commitId, "" ) && !Objects.equals(commitId, null)){
            Commit commit = Commit.deserialize(commitId);
            if(commit == null) {
                System.out.println("fatal: unknown commit '" + commitId + "'.");
                return commitSet;
            }
            commitId = commit.getParent();
        }
        return commitSet;
    }

    public static void deleteUntrackedFiles(String parentTree){
        File parentFile = new File(Repository.getWorkTree() + File.separator + parentTree);
        String[] fileList = parentFile.list();
        assert fileList != null;
        for (String fileName : fileList) {
            String newPath = Objects.equals(parentTree, ".") ? fileName : parentTree + File.separator + fileName;
            String keyPath = newPath.replace(File.separator, Tree.treeSeparator);
            if(Objects.equals(index.getStatus(keyPath), JitIndex.untracked)){
                FileDeletion.deleteFile(Repository.getWorkTree() + File.separator + newPath);
            }
        }
    }

    public static void changeStatusInIndex(String keyPath){
        if(commitMap.containsKey(keyPath)) {
            index.putStatus(keyPath, JitIndex.modified);
        }
        else{
            index.putStatus(keyPath, JitIndex.newFile);
        }
    }

    public static void changeStatusInCommit(String keyPath){
        index.putStatus(keyPath, JitIndex.added);
    }

    public static void clearUntrackedStatus(){
        ArrayList<String> untrackedPath = new ArrayList<>();
        for (Map.Entry<String, Integer> item : index.getStatusValues()) {
            String keyPath = item.getKey();
            if(Objects.equals(index.getStatus(keyPath), JitIndex.untracked))
                untrackedPath.add(keyPath);
        }
        for(String deletePath : untrackedPath){
            index.removeStatus(deletePath);
        }
    }

    /**
     * To use this function, please clear all untracked files in statusMap, use clearUntrackedStatus to clear it.
     * @param parentTree the parent path of the directory.
     */
    public static void setUntrackedToStatus(String parentTree){
        File parentFile = new File(Repository.getWorkTree() + File.separator + parentTree);
        String[] fileList = parentFile.list();
        if(fileList != null)
            for (String fileName : fileList) {
                if(Objects.equals(fileName, ".jit"))continue;
                String newPath = Objects.equals(parentTree, ".") ? fileName : parentTree + File.separator + fileName;
                String keyPath = newPath.replace(File.separator, Tree.treeSeparator);
                if(!index.containsStatus(keyPath)){
                    index.putStatus(keyPath, JitIndex.untracked);
                }
                else if(new File(Repository.getWorkTree() + File.separator + newPath).isDirectory()){
                    setUntrackedToStatus(newPath);
                }
            }
        else{
            System.out.printf("warning: empty folder '%s'\n", parentTree);
        }
    }

    public static void safeClearIndex(){
        Set<Map.Entry<String, String>> addedItemsSet = index.getMapValues();
        index.clearIndex();
        for (Map.Entry<String, String> item : addedItemsSet) {
            String keyPath = item.getKey();
            if(commitMap.containsKey(keyPath))
                index.putStatus(keyPath, JitIndex.added);
            else
                index.putStatus(keyPath, JitIndex.untracked);
            judgePathUntracked(keyPath);
        }
    }

    public static void safeCommitIndex(){
        Set<Map.Entry<String, String>> addedItemsSet = index.getMapValues();
        index.clearIndex();
        for (Map.Entry<String, String> item : addedItemsSet) {
            String keyPath = item.getKey();
            index.putStatus(keyPath, JitIndex.added);
        }
    }

    public static boolean safeRemoveIndex(String keyPath){
        boolean isRemove = index.removeIndex(keyPath);
        if(isRemove) {
            if (commitMap.containsKey(keyPath))
                index.putStatus(keyPath, JitIndex.added);
            else
                index.putStatus(keyPath, JitIndex.untracked);
            judgePathUntracked(keyPath);
        }
        return isRemove;
    }

    public static void judgePathUntracked(String keyPath){
        Set<Map.Entry<String, String>> addedItemsSet = index.getMapValues();
        ArrayList<String> indexList = new ArrayList<>();
        for (Map.Entry<String, String> item : addedItemsSet) {
            indexList.add(item.getKey());
        }
        Set<Map.Entry<String, String>> committedItemsSet = commitMap.entrySet();
        ArrayList<String> commitList = new ArrayList<>();
        for (Map.Entry<String, String> item : committedItemsSet) {
            commitList.add(item.getKey());
        }
        commitList.addAll(indexList);
        String[] keyPathSplit = keyPath.split(Tree.treeSeparator);
        int len = keyPathSplit.length;
        String parentPath = keyPath;
        for(int i = len-1; i >= 1; i--){
            int subLength = keyPathSplit[i].length();
            int parentLength = parentPath.length();
            parentPath = parentPath.substring(0, parentLength - subLength);
            boolean pathHasBlob = false;
            for(String otherPath : commitList){
                if(otherPath.indexOf(parentPath) == 0) {
                    pathHasBlob = true;
                    break;
                }
            }
            parentPath = parentPath.substring(0, parentLength - Tree.treeSeparator.length());
            if(!pathHasBlob)
                index.putStatus(parentPath, JitIndex.untracked);
            else break;
        }
    }
}
